/*
 * Zed Attack Proxy (ZAP) and its related class files.
 *
 * ZAP is an HTTP/HTTPS proxy for assessing web application security.
 *
 * Copyright 2013 The ZAP Development Team
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.zaproxy.zap.extension.sessions;

import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.Vector;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import org.apache.log4j.Logger;
import org.parosproxy.paros.Constant;
import org.parosproxy.paros.model.Session;
import org.zaproxy.zap.model.Context;
import org.zaproxy.zap.session.AbstractSessionManagementMethodOptionsPanel;
import org.zaproxy.zap.session.SessionManagementMethod;
import org.zaproxy.zap.session.SessionManagementMethodType;
import org.zaproxy.zap.utils.FontUtils;
import org.zaproxy.zap.view.AbstractContextPropertiesPanel;
import org.zaproxy.zap.view.LayoutHelper;

/** The Context Panel shown for configuring a Context's session management method. */
public class ContextSessionManagementPanel extends AbstractContextPropertiesPanel {

    /** The Constant PANEL NAME. */
    private static final String PANEL_NAME =
            Constant.messages.getString("sessionmanagement.panel.title");

    private static final String CONFIG_NOT_NEEDED =
            Constant.messages.getString("sessionmanagement.panel.label.noConfigPanel");

    private static final Logger log = Logger.getLogger(ContextSessionManagementPanel.class);

    /** The Constant serialVersionUID. */
    private static final long serialVersionUID = 6125457981814742851L;

    /** The extension. */
    private ExtensionSessionManagement extension;

    /** The session management methods combo box. */
    private JComboBox<SessionManagementMethodType> sessionManagementMethodsComboBox;

    /** The selected method that is being configured. */
    private SessionManagementMethod selectedMethod;

    /** The shown method type. */
    private SessionManagementMethodType shownMethodType;

    /** The shown method's config panel. */
    private AbstractSessionManagementMethodOptionsPanel shownConfigPanel;

    /** The container panel for the configuration. */
    private JPanel configContainerPanel;

    /**
     * Instantiates a new context session management panel.
     *
     * @param extension the extension
     * @param context the context
     */
    public ContextSessionManagementPanel(ExtensionSessionManagement extension, Context context) {
        super(context.getId());
        this.extension = extension;
        initialize();
    }

    /** Initialize the panel. */
    private void initialize() {
        this.setLayout(new CardLayout());
        this.setName(getContextId() + ": " + PANEL_NAME);
        this.setLayout(new GridBagLayout());
        this.setBorder(new EmptyBorder(2, 2, 2, 2));

        this.add(
                new JLabel(
                        Constant.messages.getString("sessionmanagement.panel.label.description")),
                LayoutHelper.getGBC(0, 0, 1, 1.0D));

        // Session management combo box
        this.add(
                new JLabel(Constant.messages.getString("sessionmanagement.panel.label.typeSelect")),
                LayoutHelper.getGBC(0, 1, 1, 1.0D, new Insets(20, 0, 5, 5)));
        this.add(getSessionManagementMethodsComboBox(), LayoutHelper.getGBC(0, 2, 1, 1.0D));

        // Method config panel container
        this.add(
                getConfigContainerPanel(),
                LayoutHelper.getGBC(0, 3, 1, 1.0d, new Insets(10, 0, 10, 0)));

        // Padding
        this.add(new JLabel(), LayoutHelper.getGBC(0, 99, 1, 1.0D, 1.0D));
    }

    /**
     * Changes the shown method's configuration panel (used to display brief info about the method
     * and configure it) with a new one, based on a new method type. If {@code null} is provided as
     * a parameter, nothing is shown. If the provided method type does not require configuration, a
     * simple message is shown stating that no configuration is needed.
     *
     * @param newMethodType the new method type. If null, nothing is shown.
     */
    private void changeMethodConfigPanel(SessionManagementMethodType newMethodType) {
        // If there's no new method, don't display anything
        if (newMethodType == null) {
            getConfigContainerPanel().removeAll();
            getConfigContainerPanel().setVisible(false);
            this.shownMethodType = null;
            this.shownConfigPanel = null;
            return;
        }

        // If a panel of the correct type is already shown, do nothing
        if (shownMethodType != null
                && newMethodType.getClass().equals(shownMethodType.getClass())) {
            return;
        }

        log.debug("Creating new panel for configuring: " + newMethodType.getName());
        this.getConfigContainerPanel().removeAll();

        // show the panel according to whether the session management type needs configuration
        if (newMethodType.hasOptionsPanel()) {
            this.shownConfigPanel = newMethodType.buildOptionsPanel(getUISharedContext());
            getConfigContainerPanel().add(this.shownConfigPanel, BorderLayout.CENTER);
        } else {
            this.shownConfigPanel = null;
            getConfigContainerPanel()
                    .add(
                            new JLabel("<html><p>" + CONFIG_NOT_NEEDED + "</p></html>"),
                            BorderLayout.CENTER);
        }
        this.shownMethodType = newMethodType;

        this.getConfigContainerPanel().setVisible(true);
        this.getConfigContainerPanel().revalidate();
    }

    /**
     * Gets the session management methods combo box.
     *
     * @return the session management methods combo box
     */
    protected JComboBox<SessionManagementMethodType> getSessionManagementMethodsComboBox() {
        if (sessionManagementMethodsComboBox == null) {
            Vector<SessionManagementMethodType> methods =
                    new Vector<>(extension.getSessionManagementMethodTypes());
            sessionManagementMethodsComboBox = new JComboBox<>(methods);
            sessionManagementMethodsComboBox.setSelectedItem(null);

            // Prepare the listener for the change of selection
            sessionManagementMethodsComboBox.addItemListener(
                    new ItemListener() {

                        @Override
                        public void itemStateChanged(ItemEvent e) {
                            if (e.getStateChange() == ItemEvent.SELECTED) {
                                // Prepare the new session management method
                                log.debug("Selected new Session Management type: " + e.getItem());
                                SessionManagementMethodType type =
                                        ((SessionManagementMethodType) e.getItem());

                                // If no session management method was previously selected or it's a
                                // different class, create it now
                                if (selectedMethod == null
                                        || !type.isTypeForMethod(selectedMethod)) {
                                    // Create the new session management method
                                    selectedMethod =
                                            type.createSessionManagementMethod(getContextId());
                                }

                                // Show the status panel and configuration button, if needed
                                changeMethodConfigPanel(type);
                                if (type.hasOptionsPanel())
                                    shownConfigPanel.bindMethod(selectedMethod);
                            }
                        }
                    });
        }
        return sessionManagementMethodsComboBox;
    }

    /**
     * Gets the configuration container panel.
     *
     * @return the config container panel
     */
    private JPanel getConfigContainerPanel() {
        if (configContainerPanel == null) {
            configContainerPanel = new JPanel(new BorderLayout());
            configContainerPanel.setBorder(
                    javax.swing.BorderFactory.createTitledBorder(
                            null,
                            Constant.messages.getString("sessionmanagement.panel.config.title"),
                            javax.swing.border.TitledBorder.DEFAULT_JUSTIFICATION,
                            javax.swing.border.TitledBorder.DEFAULT_POSITION,
                            FontUtils.getFont(FontUtils.Size.standard),
                            java.awt.Color.black));
        }
        return configContainerPanel;
    }

    @Override
    public String getHelpIndex() {
        return "ui.dialogs.contexts";
    }

    @Override
    public void initContextData(Session session, Context uiSharedContext) {
        selectedMethod = uiSharedContext.getSessionManagementMethod();
        if (log.isDebugEnabled())
            log.debug(
                    "Initializing configuration panel for session management method: "
                            + selectedMethod
                            + " for context "
                            + uiSharedContext.getName());

        // If something was already configured, find the type and set the UI accordingly
        if (selectedMethod != null) {
            // If the proper type is already selected, just rebind the data
            if (shownMethodType != null && shownMethodType.isTypeForMethod(selectedMethod)) {
                if (shownMethodType.hasOptionsPanel()) shownConfigPanel.bindMethod(selectedMethod);
                return;
            }

            // Select what needs to be selected
            for (SessionManagementMethodType type : extension.getSessionManagementMethodTypes())
                if (type.isTypeForMethod(selectedMethod)) {
                    // Selecting the type here will also force the selection listener to run and
                    // change the config panel accordingly
                    getSessionManagementMethodsComboBox().setSelectedItem(type);
                    break;
                }
        }
    }

    @Override
    public void validateContextData(Session session) throws IllegalStateException {
        if (selectedMethod == null)
            throw new IllegalStateException(
                    "A valid session management method has to be selected for Context "
                            + getUISharedContext().getName());
        if (shownConfigPanel != null) shownConfigPanel.validateFields();
    }

    @Override
    public void saveContextData(Session session) throws Exception {
        if (shownConfigPanel != null) shownConfigPanel.saveMethod();
        session.getContext(getContextId()).setSessionManagementMethod(selectedMethod);
    }

    @Override
    public void saveTemporaryContextData(Context uiSharedContext) {
        if (shownConfigPanel != null) shownConfigPanel.saveMethod();
        uiSharedContext.setSessionManagementMethod(selectedMethod);
    }
}
